#pragma once

#include<iostream>
#include<string>
#include<vector>
#include<fstream>
#include<unordered_map>
#include<mutex>
#include"util.hpp"
#include"log.hpp"


namespace ns_index
{
    //倒排 + 正排：关键字 → 文档ID → 文档内容

    // 1. 正排：文档ID → 文档内容
    typedef struct docInfo
    {
        uint64_t doc_id;
        std::string title;
        std::string url;
        std::string content;
    }docInfo;

    // 2.倒排：关键字 → 文档ID(权重)
    typedef struct inverted_index_item
    {
        std::string keyword;
        uint64_t doc_id;
        int weight;
        inverted_index_item():weight(0){}
    }inverted_index_item;

    //倒排拉链  
    typedef std::vector<inverted_index_item> inverted_index_list;

    class Index
    {
        private:
            //正排索引-数组：数组下标-Id
            std::vector<docInfo> forward_index; 
            //倒排索引 - 1个keyword 映射 一组doc_id（倒排拉链）
            std::unordered_map<std::string, inverted_index_list> inverted_index;
        private:
            //单例模式：不想让索引对象频繁的创建和销毁
            // 构造函数设为私有
            Index() {}   
            //C++11的delete关键字修饰将该类的拷贝构造和拷贝赋值，以达到不能被拷贝的目的
            Index(const Index&) = delete;
            Index& operator=(const Index&) = delete;

            //懒汉模式：提供一个指向单例对象的static指针
            static Index* instance;
            static std::mutex mtx;

        public:
            ~Index() {}
        public:
            //线程安全问题：多个线程就会各自创建出一个对象
            // 双检查加锁，详情请阅读README.md
            //只有GetInstance函数第一次被调用，需要使用互斥锁进行保护，其余只是一个读操作
            static Index* GetInstance()
            {
                if(nullptr == instance) //避免大量无意义的加锁解锁
                {
                    mtx.lock();
                    if(nullptr == instance)
                    {
                        instance = new Index();
                    }
                    mtx.unlock();
                }
                return instance;
            }


            //正排：文档ID → 文档内容
            docInfo* GetForwardIndex(uint64_t doc_id)
            {
                if(doc_id >= forward_index.size())
                {
                    LOG(ERROR, "GetForwardIndex doc_id out of range");
                    return nullptr;
                }
                return &forward_index[doc_id];
            }

            //倒排：关键字 → 倒排拉链
            inverted_index_list* GetInvertIndex(const std::string& keyword)
            {
                auto iter = inverted_index.find(keyword);
                if(iter == inverted_index.end())
                {
                    LOG(ERROR, "keyword does not have invertIndex");
                    return nullptr;
                }
                return &(iter->second);
            } 

            //根据去标签的文档，构建正+倒排索引
            bool Indexing(const std::string& file_item_list_path)
            {
                std::ifstream ifs(file_item_list_path, std::ios::in | std::ios::binary);
                if(!ifs.is_open())
                {
                    LOG(ERROR, "Indexing open file " + file_item_list_path + " failed");
                    return false;
                }

                //读取每一行，构建正排和倒排
                std::string line;
                int count = 0;
                while(std::getline(ifs, line))
                {
                    docInfo* doc = BuildForwardIndex(line);
                    if(nullptr == doc)
                    {
                        LOG(ERROR, "building " + line + " error");
                        return false;
                    }
                    BuildInvertedIndex(*doc);
                    count++;
                    if(count%100 == 0)
                    {
                        LOG(NORMAL, "already building " + std::to_string(count) + " index ...");
                    }
                }
                ifs.close();
                return true;
            }
            private:
                docInfo* BuildForwardIndex(const std::string& line)
                {
                    // doc_id 和 docInfo建立映射 写入结构体
                    //1. 切分字符串
                    std::vector<std::string> divided_str;
                    const std::string sep = "\3";
                    util::StringUtil::Split(line, &divided_str, sep);
                    if(divided_str.size() != 3)
                    {
                        LOG(ERROR, "BuildForwardIndex divide line failed");
                        return nullptr;
                    }

                    //2. 填充到docInfo
                    docInfo doc;
                    doc.doc_id = forward_index.size();
                    doc.title = divided_str[0];
                    doc.url = divided_str[1];
                    doc.content = divided_str[2];

                    //3. 插入到正排索引vector中 forward_index
                    forward_index.push_back(std::move(doc));
                    return &forward_index.back();
                } 

                bool BuildInvertedIndex(const docInfo& doc)
                {
                    //建立映射：keyword - docId们 (倒排拉链) 
                    
                    // 1. 对于title, content分词 得到分词数组
                    std::vector<std::string> title_words;
                    std::vector<std::string> content_words;
                    util::JiebaUtil::CutString(doc.title, &title_words);
                    util::JiebaUtil::CutString(doc.content, &content_words);

                    //2. 统计词频
                    typedef struct word_cnt 
                    {
                        int title_cnt;
                        int content_cnt;
                        word_cnt():title_cnt(0), content_cnt(0) {}
                    }word_cnt;

                    //用来暂存词频的映射表
                    std::unordered_map<std::string, word_cnt> word_count_map;

                    // 边遍历得到的“分词数组”中的每个单词，边统计词频，构建两者映射
                    for(auto str: title_words)
                    {
                        boost::to_lower(str); //忽略大小写查询
                        word_count_map[str].title_cnt++; //存在则直接获取，不存在则先新建
                    }

                    for(auto str: content_words)
                    {
                        boost::to_lower(str);
                        word_count_map[str].content_cnt++;
                    }

                    //3. 自定义相关性 - 计算权重
#define TITLE_W 10
#define CONTENT_W 1
                    
                    //遍历映射<k,v>, 将信息填入inverted_index_item中(keyword, doc_id, weight)
                    for(auto& word_kv: word_count_map)
                    {
                        //keyword 对应 id, weight，存储在item中
                        inverted_index_item item;
                        item.doc_id = doc.doc_id;
                        item.keyword = word_kv.first;
                        item.weight = TITLE_W*word_kv.second.title_cnt + CONTENT_W*word_kv.second.content_cnt;

                        // 每个keyword 对应 多个id（weight）
                        // typedef std::vector<inverted_index_item> inverted_index_list;
                        // std::unodered_map<std::string, inverted_index_list> inverted_index;
                        // 多个(keyword, id,weight）加入 inverted_index_list(倒排拉链) 中
                        inverted_index_list& index_list = inverted_index[word_kv.first];
                        index_list.push_back(std::move(item));
                    }

                    return true;
                }

    };
    Index* Index::instance = nullptr; //在程序入口之前先将static指针初始化为空
    std::mutex Index::mtx; //初始化互斥锁
}